﻿//-----------------------------------------------------------------------
// <copyright file="ApplicationForm.cs" company="Built to Roam">
//     Copyright © Built to Roam 2010. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using System;
using System.ComponentModel;
using System.Threading;
using MeizuSDK.Drawing.OpenGLES;
using MeizuSDK.Presentation;

namespace Demo
{
    abstract class EglWindow : DemoWindowBase
    {
        private GLESDevice device;

        private EGLDisplay _display;
        private EGLSurface _surface;
        private EGLContext _context;

        /// <summary>
        /// This represents the system tick count when the update method
        /// was last called
        /// </summary>
        private float lastUpdate = 0.0f;

        /// <summary>
        /// This indicates whether an update is in progress to prevent multiple
        /// entries into the update method. Note that Update is non-blocking, meaning
        /// that there may be multiple OnPaint iterations whilst the scene is being 
        /// updated.
        /// </summary>
        private int isUpdating = 0;

        /// <summary>
        /// Used to prevent rendering exceptions being raised when the form is being closed
        /// </summary>
        private bool isClosing = false;

        /// <summary>
        /// Initializes a new instance of the ApplicationForm class that
        /// is used to encapsulate the WinForms functionality (eg OnPaint)
        /// and expose virtual methods for Setup, Update and Draw
        /// </summary>
        public EglWindow()
        {
            device = new GLESDevice();
        }

        /// <summary>
        /// 窗口加载
        /// </summary>
        /// <param name="e"></param>
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            // Setup the OpenGL Display
            if (!device.InitDevice(HWnd, false))
            {
                this.Close();
                return;
            }

            //InitializeDisplay();

            // Set some initial OpenGL parameters to control the way
            // the display works
            InitializeOpenGL();

            // Setup the scene so that it can be drawn. Note: It's important
            // to setup the scene here because UpdateScene will NOT be called
            // prior to the first draw.
            SetupScene();
        }

        /// <summary>
        /// Issues necessary calls to setup OpenGLES, create the Display,
        /// Surface and Context that are required for OpenGLEs to
        /// render to the screen
        /// </summary>
        private void InitializeDisplay()
        {
            try
            {
                // Retrieve the display to use within EGL calls
                _display = egl.GetDisplay(new EGLNativeDisplayType(this.GetDC()));
                int major, minor;

                // Initialize EGL on this display. Major and Minor return
                // the version of EGL
                if (!egl.Initialize(_display, out major, out minor))
                {
                    throw new InvalidOperationException("Unable to initialize display.");
                }

                // Setup the EGL configuration
                EGLConfig[] configs = new EGLConfig[10];
                int[] attribList = new int[] 
                { 
                    egl.EGL_RED_SIZE, 5, 
                    egl.EGL_GREEN_SIZE, 6, 
                    egl.EGL_BLUE_SIZE, 5, 
                    egl.EGL_DEPTH_SIZE, 16 , 
                    egl.EGL_SURFACE_TYPE, egl.EGL_WINDOW_BIT,
                    egl.EGL_STENCIL_SIZE, egl.EGL_DONT_CARE,
                    egl.EGL_NONE, egl.EGL_NONE 
                };

                int numConfig;
                if (!egl.ChooseConfig(_display, attribList, configs, configs.Length, out numConfig) || numConfig < 1)
                    throw new InvalidOperationException("Unable to choose config.");

                EGLConfig config = configs[0];

                // Create the EGL surface and context
                /* 3.5.1 Creating On-Screen Rendering Surfaces
                    eglCreateWindowSurface creates an onscreen EGLSurface and returns a handle
                    to it. Any EGL context created with a compatible EGLConfig can be used to
                    render into this surface.*/
                _surface = egl.CreateWindowSurface(_display, config, Handle, null);
                _context = egl.CreateContext(_display, config, EGLContext.None, null);

                // Set the surface and context to be current on the display
                egl.MakeCurrent(_display, _surface, _surface, _context);
            }
            catch
            {
                MeizuSDK.Presentation.MessageBox.Show("Unable to initialize application so exiting");
                this.Close();
                return;
            }
        }

        /// <summary>
        /// Sets up some initial OpenGL parameters. These are specific to OpenGL
        /// rather than to the Windows Mobile implementation.
        /// </summary>
        void InitializeOpenGL()
        {
            /* GL primitives can have either flat or smooth shading. Smooth shading, 
             * the default, causes the computed colors of vertices to be interpolated
             * as the primitive is rasterized, typically assigning different colors 
             * to each resulting pixel fragment. Flat shading selects the computed color of just one vertex and assigns it to all the pixel fragments generated by rasterizing a single primitive. In either case, the computed color of a vertex is the result of lighting if lighting is enabled, or it is the current color at the time the vertex was specified if lighting is disabled. */
            //gl.ShadeModel(gl.GL_SMOOTH);

            /* glClearColor specifies the red, green, blue, and alpha values used by 
             * glClear to clear the color buffers. Values specified by glClearColor are clamped to the range [0,1]. */
            gl.ClearColor(0.0f, 0.0f, 0.0f, 0.5f);

            /* glClearDepth specifies the depth value used by glClear to clear the depth 
             * buffer. Values specified by glClearDepth are clamped to the range [0,1].*/
            gl.ClearDepthf(1.0f);

            /* glDepthFunc specifies the function used to compare each incoming pixel 
             * depth value with the depth value present in the depth buffer. The comparison is 
             * performed only if depth testing is enabled. (See glEnable and glDisable of GL_DEPTH_TEST.) */
            gl.DepthFunc(gl.GL_LEQUAL);

            /* The alpha test discards fragments depending on the outcome of a comparison 
             * between an incoming fragment's alpha value and a constant reference value. 
             * glAlphaFunc specifies the reference value and the comparison function. The 
             * comparison is performed only if alpha testing is enabled. By default, it is 
             * not enabled. (See glEnable and glDisable of GL_ALPHA_TEST.) */
            //gl2.glAlphaFunc(gl.GL_GREATER, 0.5f);

            /* glEnable and glDisable enable and disable various capabilities. Use glIsEnabled 
             * or glGet to determine the current setting of any capability. The initial value 
             * for each capability with the exception of GL_DITHER and GL_MULTISAMPLE is GL_FALSE. 
             * The initial value for GL_DITHER and GL_MULTISAMPLE is GL_TRUE. */
            gl.Enable(gl.GL_DEPTH_TEST);
            gl.Enable(gl.GL_BLEND);

        }

        /// <summary>
        /// Handles the windows message pump in order to refresh the display. The process
        /// is relatively straight forward. Call DrawScene to render the current scene, then 
        /// call UpdateScene to update the scene. However, it's important to note that
        /// UpdateScene is called on a separate thread to ensure there is no blocking on the 
        /// UI thread. 
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);

            // Draw the current scene
            RunDrawScene();

            egl.SwapBuffers(device.Display, device.Surface);
            //egl.SwapBuffers(_display, _surface);

            gl2.glClear(gl2.GL_COLOR_BUFFER_BIT | gl2.GL_DEPTH_BUFFER_BIT);

            // Update the current scene
            RunUpdateScene();
        }

        /// <summary>
        /// Sets up the initial matrices for drawing the scene then
        /// calls the virtual DrawScene method that should be overrided
        /// in custom forms to draw the current scene
        /// </summary>
        private void RunDrawScene()
        {
            // Reset the viewport and both projection and modelview matrices
            gl2.glViewport(ClientRectangle.Left, ClientRectangle.Top, ClientRectangle.Width, ClientRectangle.Height);
            //gl.MatrixMode(gl.GL_PROJECTION);
            //gl.LoadIdentity();
            //gl.Orthof(0, Width, Height - ToolBar.HEIGHT, 0, -10, 10);

            //gl.MatrixMode(gl.GL_MODELVIEW);
            //gl.LoadIdentity();

            DrawScene();
        }

        /// <summary>
        /// Handles setting up a separate thread for the UpdateScene to be invoked
        /// from. This method also ensures that there is only ever a single instance
        /// of the UpdateScene in operation.
        /// </summary>
        private void RunUpdateScene()
        {
            // Check for running instance - exit if already running
            if (Interlocked.CompareExchange(ref isUpdating, 1, 0) == 1)
            {
                return;
            }

            // Only instance running, so create the thread in which to
            // invoke the UpdateScene method
            ThreadPool.QueueUserWorkItem((async) =>
            {
                try
                {
                    // Calculate the time in seconds since last update
                    var seconds = (float)Environment.TickCount / 1000.0f;
                    var diff = 0.0f;
                    if (this.lastUpdate > 0)
                    {
                        diff = seconds - this.lastUpdate;
                    }
                    this.lastUpdate = seconds;

                    // Invoke the virtual UpdateScene method
                    UpdateScene(diff);
                }
                finally
                {
                    // Make sure this method can be re-entered
                    Interlocked.Decrement(ref isUpdating);

                    // Only loop if the form isn't being closed.
                    if (!this.isClosing)
                    {
                        try
                        {
                            // Invoke "Invalidate" on the control which will cause
                            // the control to be refreshed (ie OnPaint called) leading
                            // to another iteration of Draw and Update
                            Invalidate();
                        }
                        catch (ObjectDisposedException)
                        {
                            // Disregard this as it may happen when exiting. Although
                            // we are setting the isClosing flag, there is still a 
                            // chance that the application will close before the
                            // update method has completed.
                        }
                    }
                }
            });
        }


        /// <summary>
        /// Override OnPaintBackground to remove flickering in display
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPaintBackground(PaintEventArgs e) { }

        /// <summary>
        /// Handles the closing event for the form to ensure the OpenGL 
        /// references to Display, Surface and Context are freed. Not
        /// doing this may leave the device in an unstable state as 
        /// resources are not being correctly released
        /// </summary>
        /// <param name="e"></param>
        protected override void OnClosing(CancelEventArgs e)
        {
            isClosing = true;

            //// Destroy surface and contexts
            //if (!egl.DestroySurface(_display, _surface))
            //    throw new Exception("Error while destroying surface.");
            //if (!egl.DestroyContext(_display, _context))
            //    throw new Exception("Error while destroying context.");

            //// Terminate EGL and release the display
            //if (!egl.Terminate(_display))
            //    throw new Exception("Error while terminating display.");

            device.DestroyDevice();

            base.OnClosing(e);
        }


        /// <summary>
        /// Called once during application start up to setup the scene
        /// prior to the first draw
        /// 设置场景
        /// </summary>
        protected virtual void SetupScene() { }

        /// <summary>
        /// Called to update the scene. Note that it may not be on every draw
        /// depending on how long the UpdateScene method takes to complete
        /// and whether there were other system events that causes OnPaint to 
        /// be called.
        /// </summary>
        /// <param name="secondsSinceLastUpdate"></param>
        protected virtual void UpdateScene(float secondsSinceLastUpdate) { }

        /// <summary>
        /// Called to draw the scene. Again note that this method may be 
        /// called multiple times between or even during an UpdateScene.
        /// </summary>
        protected virtual void DrawScene() { }

    }
}